﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml;

namespace IMPression
{
	public class EquationParser
	{
		Term m_term = new Term();

	    public static bool UseDegrees = false;

		public static List<string> FunctionsList 
		{
			get { return Function.FunctionsList;}
		}
		public string Equation {get; private set;}

		public double Value
		{
			get { return m_term.Value; }
		}
		public string ValueAsString
		{
			get 
			{ 
				return Value.ToString();
			}
		}
		public string UsedVarsAsString
		{
			get
			{	var sb = new StringBuilder();
				var vars = m_term.GetUsedVars();
				foreach (var var in vars)
					sb.AppendFormat("{0}={1}, ", var, Math.Round(m_term.GetVar(var),3));
				return sb.ToString();
			}
		}
		public void SetLastAnswer(double value)
		{
			m_term.SetVar("ans", value);
		}
		public double Calculate(string equation)
		{
			return Calculate(DeCleanUp(equation), string.Empty);
		}
		public double Calculate(string equation, string variablestring)
		{
			equation = DeCleanUp(equation);

			if (variablestring.Length > 0)
			{
				foreach (var varstring in variablestring.Split(';'))
				{
					var split = varstring.Split('=');
				    if (split.Length == 1 && split[0].Trim().Length == 0)
				        continue;
				    if (split.Length != 2)
						throw new ParseException(variablestring, 0, "La liste doit être séparée par des points-virgules, ex. 'x=10; y=12'");
					var varname = split[0].Trim();
					var vareq = split[1].Trim();
					var v = new Term();
					v.Parse(vareq);
					m_term.SetVar(varname, v.Value);
				}
			}
			m_term.Parse(equation);
		    return m_term.Value;
		}
		public double CalcForVar(string varname, double varvalue)
		{
			m_term.SetVar(varname, varvalue);
            return Math.Abs(m_term.Value) < Math.Pow(10, -15) ? 0 : m_term.Value;
		}
		public string CleanUp(string expr)
		{
			var res = expr;

			res = res.Replace("sqrt", "√");
            res = res.Replace("curt", "∛");

		    res = res.Replace("sum", "Σnx");

		    res = res.Replace("catalan", "K");
		    res = res.Replace("eulergamma", "γ");
		    res = res.Replace("digamma", "Ψ");
		    res = res.Replace("inv", "⁻¹");
		    res = res.Replace("gamma", "Γ");

            res = res.Replace("arcsinh", "sinh⁻¹");
            res = res.Replace("arccosh", "cosh⁻¹");
            res = res.Replace("arctanh", "tanh⁻¹");
            res = res.Replace("arccoth", "coth⁻¹");
            res = res.Replace("arccsch", "csch⁻¹");
            res = res.Replace("arcsech", "sech⁻¹");
			res = res.Replace("arcsin", "sin⁻¹");
			res = res.Replace("arccos", "cos⁻¹");
			res = res.Replace("arctan", "tan⁻¹");
			res = res.Replace("arccot", "cot⁻¹");
			res = res.Replace("arccsc", "csc⁻¹");
			res = res.Replace("arcsec", "sec⁻¹");

		    res = res.Replace("log10", "log₁₀");

		    res = Regex.Replace(res, "(?<![a-zA-Z])pi(?![a-zA-Z])", "π");
		    res = res.Replace("phi", "φ");

			return res;
		}

        

		public string DeCleanUp(string expr)
		{
			var res = expr;

            res = res.Replace("∛", "curt");
		    res = res.Replace("√", "sqrt");

		    res = res.Replace("Σnx", "sum");

		    res = res.Replace("K", "catalan");
            res = res.Replace("Ψ", "digamma");
            
		    res = res.Replace("Γ", "gamma");
		    res = res.Replace("γ", "eulergamma");

			res = res.Replace("sin⁻¹", "arcsin");
			res = res.Replace("cos⁻¹", "arccos");
			res = res.Replace("tan⁻¹", "arctan");
			res = res.Replace("cot⁻¹", "arccot");
			res = res.Replace("csc⁻¹", "arccsc");
			res = res.Replace("sec⁻¹", "arcsec");
			res = res.Replace("sinh⁻¹", "arcsinh");
			res = res.Replace("cosh⁻¹", "arccosh");
			res = res.Replace("tanh⁻¹", "arctanh");
			res = res.Replace("coth⁻¹", "arccoth");
			res = res.Replace("csch⁻¹", "arccsch");
			res = res.Replace("sech⁻¹", "arcsech");

            res = res.Replace("⁻¹", "inv");

		    res = res.Replace("log₁₀", "log10");

			res = Regex.Replace(res, @"([\xB2\xB3\xB9\u2070\u2074-\u207B]+)", delegate(Match match)
			{
				var resu = "^(";
				match.ToString().All(x =>
				{
					resu += suptonb(x);
					return true;
				});
				resu += ")";
				return resu;
			});

			res = res.Replace("π", "pi");
		    res = res.Replace("φ", "phi");

			res = res.Replace("÷", "/");
			res = res.Replace("×", "*");

			return res;
		}

	char suptonb(char sup)
	{
		if (sup == '\xB2')
			return '2';
		if (sup == '\xB3')
			return '3';
		if (sup == '\xB9')
			return '1';

		if (sup == '\u2070')
				 return '0';
		if (sup == '\u2074')
			return '4';
		if (sup == '\u2075')
			return '5';
		if (sup == '\u2076')
			return '6';
		if (sup == '\u2077')
			return '7';
		if (sup == '\u2078')
			return '8';
		if (sup == '\u2079')
			return '9';
		if (sup == '\u207A')
			return '+';
		if (sup == '\u207B')
			return '-';

		return ' ';
	}

/*
		string NextWord(string equation, ref int startindex)
		{
			while (startindex < equation.Length && equation[startindex] == ' ')
				startindex++;
			if (startindex >= equation.Length)
				return string.Empty;
			var sb = new StringBuilder();
			while (startindex < equation.Length)
			{
				var ch = equation[startindex];
				if (sb.Length > 0 && ch == '=')
					return sb.ToString();
				startindex++;
				if (sb.Length == 0 && ch == '=')
					return "=";
				if (char.IsLetterOrDigit(ch) || ch=='.' || ch=='-')
					sb.Append(ch);
				else
					return sb.ToString();
			}
			return sb.ToString();
		}
*/


		public void Write(XmlTextWriter wr)
		{
			wr.WriteStartElement("eqp");
			wr.WriteElementString("eq", Equation);
			var vars = m_term.GetUsedVars();
			if (vars.Length > 0)
			{
				foreach (var var in vars)
				{
					wr.WriteStartElement("var");
					wr.WriteAttributeString("name", var);
					wr.WriteAttributeString("value", XmlConvert.ToString(m_term.GetVar(var)));
					wr.WriteEndElement();
				}
			}
			wr.WriteEndElement();
		}
		public void Read(XmlElement node)
		{
			if (node == null)
				return;
			Debug.Assert(node.Name == "eqp");
			foreach (XmlNode child in node.ChildNodes)
			{
				var element = child as XmlElement;
				if (element == null)
					continue;
				if (element.Name == "eq")
				{
					Equation = element.InnerText;
					continue;
				}
				if (element.Name == "var")
				{
					var name = element.GetAttribute("name");
					var value = XmlConvert.ToDouble(element.GetAttribute("value"));
					m_term.SetVar(name, value);
				}
			}
			Calculate(Equation);
		}

	}
	public class EquationStack : INotifyCollectionChanged, IList<EquationParser>
	{
		public const string xmlTag = "eqs";
		List<EquationParser> m_stack = new List<EquationParser>();
		public int MaxCount {get; set;}
		public double LastAnswer
		{
			get
			{
				if (m_stack.Count > 0)
					return m_stack[m_stack.Count-1].Value;
				return double.NaN;
			}
		}
		public void Add(EquationParser equation)
		{
			m_stack.Add(equation);
			if (CollectionChanged != null)
				CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, equation));
			if (m_stack.Count > MaxCount)
			{
				var old = m_stack[0];
				m_stack.RemoveAt(0);
				if (CollectionChanged != null)
					CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, old, 0));
			}
		}
		public event NotifyCollectionChangedEventHandler CollectionChanged;
		public EquationStack()
		{
			MaxCount = 100;
		}
		public void Write(XmlTextWriter wr)
		{
			wr.WriteStartElement(xmlTag);
			foreach (var parser in this)
				parser.Write(wr);
			wr.WriteEndElement();
		}
		public void Read(XmlElement node)
		{
			if (node == null)
				return;
			Debug.Assert(node.Name == xmlTag);
			if (node != null)
			{
				foreach (XmlNode child in node.ChildNodes)
				{
					var element = child as XmlElement;
					if (element == null)
						continue;
					var parser = new EquationParser();
					parser.Read(element);
					Add(parser);
				}
			}
		}

		#region IList<EquationParser> Members

		public int IndexOf(EquationParser item)
		{
			throw new NotImplementedException();
		}

		public void Insert(int index, EquationParser item)
		{
			throw new NotImplementedException();
		}

		public void RemoveAt(int index)
		{
			throw new NotImplementedException();
		}

		public EquationParser this[int index]
		{
			get
			{
				return m_stack[index];
			}
			set
			{
				throw new NotImplementedException();
			}
		}

		#endregion
		#region ICollection<EquationParser> Members


		public void Clear()
		{
			throw new NotImplementedException();
		}

		public bool Contains(EquationParser item)
		{
			throw new NotImplementedException();
		}

		public void CopyTo(EquationParser[] array, int arrayIndex)
		{
			throw new NotImplementedException();
		}

		public int Count
		{
			get { return m_stack.Count; }
		}

		public bool IsReadOnly
		{
			get { throw new NotImplementedException(); }
		}

		public bool Remove(EquationParser item)
		{
			throw new NotImplementedException();
		}

		#endregion
		#region IEnumerable<EquationParser> Members

		public IEnumerator<EquationParser> GetEnumerator()
		{
			return m_stack.GetEnumerator();
		}

		#endregion
		#region IEnumerable Members

		IEnumerator IEnumerable.GetEnumerator()
		{
			return m_stack.GetEnumerator();
		}

		#endregion
	}
}
